//============================================================================
// Name        : ColladaConverter.cpp
// Author      : Kirill Blinov
// Version     : 0.1
// Copyright   : LGPL
// Description : COLLADA file converter.
//============================================================================

#include <fstream>
#include <iostream>
#include <vector>
#include <cstring>
#include <boost/unordered_map.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/version.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "Types.h"

using namespace std;
using namespace pugi;

const string DEFAULT_OUTPUT_FILE = "Default.CCM";
const string DEFAULT_FINAL_MESSAGE = "Nothing to be done";
const string INVALID_ARGUMENT_MESSAGE = "Invalid argument: ";
const string COULD_NOT_LOAD_INPUT_MESSAGE = "Couldn't load input data!";
const string ALL_FINISHED_SUCCESSFULLY_MESAGE = "Input data is compressed successfully";

const char INPUT_OPTION_FIRST_SYMBOL = '-';
const int ESTIMATED_INPUTS_COUNT = 5;

const char* PRIMITIVE_POSSIBLE_NAMES[] = { "triangles", "polygons", "polylist", "tristrips", "" };

// This macro removes '#' symbol in the beginning of source ID
#define DEREFERENCE_INPUT_NAME( Name ) Name + 1

#define TO_STRING(x) #x

//#define DEBUG_INPUT

BOOST_CLASS_VERSION( LOADED_3D_INFO, 1)
BOOST_CLASS_VERSION( MESH, 1)
BOOST_CLASS_VERSION( SKINNING_CONTROLLER, 1)
BOOST_CLASS_VERSION( BONE, 1)

VERTEX_DATA_SEMANTIC
StringToDataSemantic( const char* String )
{
	VERTEX_DATA_SEMANTIC Result = VERTEX_DATA_SEMANTIC_INCORRECT;

	if ( strcmp( String, "COLOR" ) == 0 )
	{
		Result = VERTEX_DATA_SEMANTIC_COLOR;
	}
	else if ( strcmp( String, "NORMAL" ) == 0 )
	{
		Result = VERTEX_DATA_SEMANTIC_NORMAL;
	}
	else if ( strcmp( String, "POSITION" ) == 0 )
	{
		Result = VERTEX_DATA_SEMANTIC_POSITION;
	}
	else if ( strcmp( String, "TEXCOORD" ) == 0 )
	{
		Result = VERTEX_DATA_SEMANTIC_TEXCOORD;
	}
	else if ( strcmp( String, "VERTEX" ) == 0 )
	{
		Result = VERTEX_DATA_SEMANTIC_VERTEX;
	}
	else if ( strcmp( String, "WEIGHT" ) == 0 )
	{
		Result = VERTEX_DATA_SEMANTIC_WEIGHT;
	}
	else
	{
		cout << "Error: Semantic "<< String << " is not supported!" << endl;
		assert( false );
	}

	return Result;
}

DATA_TYPE
StringToDataType( const char* String )
{
	DATA_TYPE Result = DATA_TYPE_INCORRECT;

	if ( strcmp( String, "float" ) == 0 )
	{
		Result = DATA_TYPE_FLOAT;
	}
	else if ( strcmp( String, "int" ) == 0 )
	{
		Result = DATA_TYPE_INT;
	}
	else if ( strcmp( String, "float4x4" ) == 0 )
	{
		Result = DATA_TYPE_FLOAT4x4;
	}
	else if ( strcmp( String, "Name" )  == 0 )
	{
		Result = DATA_TYPE_NAME;
	}

	return Result;
}

unsigned int
BytesInType( DATA_TYPE Type )
{
	unsigned int Result = 1;
	switch ( Type )
	{
		break; case DATA_TYPE_FLOAT:
		       case DATA_TYPE_INT:
		{
			Result = 4;
		}
		break; case DATA_TYPE_FLOAT4x4:
		{
			Result = BytesInType(DATA_TYPE_FLOAT) * MATRIX4x4_ELEMENTS_COUNT;
		}
		break; default:
		{
			cout << "Error: Byte size for DATA_TYPE =  " << Type << " is not defined!" << endl;
			assert( false );
		}
	}
	return Result;
}

int
ReadNumbers( unsigned char* ResultArray,
		     const char* InputContent,
		     const DATA_TYPE DataType,
		     const unsigned int NumbersCount,
		     const unsigned int ReadingMask = 0xffffffff,
		     const unsigned int Stride = 1,
		     const unsigned int Offset = 0 )
{
	int ActuallyProcesseNumbers = 0;
	char* ContentCursor = NULL;

	switch ( DataType )
	{
		break; case DATA_TYPE_FLOAT:
		{
			float* Result = (float*)ResultArray;

			for ( unsigned int i = 0; i < NumbersCount; i++ )
			{
				float Number = strtof( ( ContentCursor == NULL ) ? InputContent : ContentCursor,
						               &ContentCursor );
				unsigned int NumInStride = i % Stride;

				if ( ( i >= Offset ) &&
					 ( ( ReadingMask & NumInStride ) == NumInStride ) )
				{
					*Result = Number;
					Result++;
					ActuallyProcesseNumbers++;
				}
			}
		}
		break; case DATA_TYPE_INT:
		{
			int* Result = (int*)ResultArray;

			for ( unsigned int i = 0; i < NumbersCount; i++ )
			{
				int Number = strtod( ( ContentCursor == NULL ) ? InputContent : ContentCursor,
						               &ContentCursor );
				unsigned int NumInStride = i % Stride;

				if ( ( i >= Offset ) &&
					 ( ( ReadingMask & NumInStride ) == NumInStride ) )
				{
					*Result = Number;
					Result++;
					ActuallyProcesseNumbers++;
				}
			}
		}
		break; case DATA_TYPE_FLOAT4x4:
		{
			ActuallyProcesseNumbers = ReadNumbers( ResultArray,
						 	 	  	  	  	  	   InputContent,
						 	 	  	  	  	  	   DATA_TYPE_FLOAT,
						 	 	  	  	  	  	   NumbersCount * MATRIX4x4_ELEMENTS_COUNT,
						 	 	  	  	  	  	   ReadingMask,
						 	 	  	  	  	  	   Stride,
						 	 	  	  	  	  	   Offset );
		}
		break; default:
		{
			assert( false );
		}
		break;
	}

	return ActuallyProcesseNumbers;
}

void
ParsePrimitiveTriangles( xml_node Primitive, vector<MODEL_DATA_INPUT*> DataInputs, MESH* NewMesh )
{
	assert( NewMesh );

	NewMesh->semanticsArray.reserve( DataInputs.size() );
	int VertexSizeInBytes = 0;
	for ( vector<MODEL_DATA_INPUT*>::const_iterator Begin = DataInputs.begin(), End = DataInputs.end();
		  Begin != End;
		  Begin++ )
	{
		MODEL_DATA_INPUT* Element = *Begin;
		NewMesh->semanticsArray.push_back( Element->dataSemantic );
		VertexSizeInBytes += BytesInType( Element->dataType ) * Element->elementsInUnit;
	}

	NewMesh->primitiveType = PRIMITIVE_TRINGLES;

	unsigned int PolygonsCount = Primitive.attribute( "count" ).as_uint();
	unsigned int TotalIndexCount = PolygonsCount * TRIANGLE_VERTEX_COUNT;

	const char* PArrayContent = Primitive.child( "p" ).child_value();
	unsigned int VertexConstructArraySize = TotalIndexCount *
			                       	   	   	DataInputs.size() *
			                       	   	   	BytesInType( DATA_TYPE_INT );
	unsigned char* VertexCostructArray = new unsigned char[VertexConstructArraySize];

	ReadNumbers( VertexCostructArray,
			     PArrayContent,
			     DATA_TYPE_INT,
			     TotalIndexCount * DataInputs.size(),
			     0xF,
			     1,
			     0 );

	NewMesh->triangleArray.reserve( TotalIndexCount );

	NewMesh->vertexArray = new unsigned char[VertexSizeInBytes * TotalIndexCount];
	NewMesh->vertexSizeBytes = VertexSizeInBytes;

	unsigned int *VertexCostructIndexesArray = (unsigned int*)VertexCostructArray;
	unsigned int CurrentActiveVertex = 0;

	for ( unsigned int i = 0; i < TotalIndexCount; i++ )
	{
		// Look in vertex cache
		unsigned int ActiveVertexIndex = (unsigned int)-1;
		for ( unsigned int j = 0; j < CurrentActiveVertex; j++ )
		{
			bool VertexFoundInCache = false;
			for ( unsigned int k = 0; k < DataInputs.size(); k++ )
			{
				unsigned int IndexInInput = VertexCostructIndexesArray[ i * DataInputs.size() + k ];
				VertexFoundInCache =
						 ( VertexCostructIndexesArray[ j * DataInputs.size() + k ] == IndexInInput );
			}

			if ( VertexFoundInCache )
			{
				ActiveVertexIndex = j;
				break;
			}
		}

		cout << i << " Active: " << ActiveVertexIndex << " CurrentActiveVertex: " << CurrentActiveVertex << endl;

		if (  (unsigned int)-1 == ActiveVertexIndex )
		{
			int OffsetFromVertexBegin = 0;
			for ( unsigned int j = 0; j < DataInputs.size(); j++ )
			{
				int IndexInInput = VertexCostructIndexesArray[ i * DataInputs.size() + j ];
				const MODEL_DATA_INPUT *DataInput = DataInputs[j];
				const int InputSize = BytesInType( DataInput->dataType ) * DataInput->elementsInUnit;

				memcpy( (void*)(NewMesh->vertexArray + ( CurrentActiveVertex * VertexSizeInBytes ) + OffsetFromVertexBegin ),
						(void*)(DataInput->dataPointer + ( IndexInInput * InputSize )),
						InputSize );

				cout << " IndexInInput[" << ( i * DataInputs.size() + j ) << "] " << IndexInInput << endl;
				OffsetFromVertexBegin += InputSize;
			}


			ActiveVertexIndex = CurrentActiveVertex;
			CurrentActiveVertex++;
		}

		NewMesh->triangleArray.push_back( ActiveVertexIndex );
	}

	NewMesh->vertexCount = CurrentActiveVertex;
}

void
ParsePrimitivePolylist( xml_node Primitive, vector<MODEL_DATA_INPUT*> DataInputs, MESH* NewMesh )
{
	assert( NewMesh );

	NewMesh->semanticsArray.reserve( DataInputs.size() );
	int VertexSizeInBytes = 0;
	for ( vector<MODEL_DATA_INPUT*>::const_iterator Begin = DataInputs.begin(), End = DataInputs.end();
		  Begin != End;
		  Begin++ )
	{
		MODEL_DATA_INPUT* Element = *Begin;
		NewMesh->semanticsArray.push_back( Element->dataSemantic );
		VertexSizeInBytes += BytesInType( Element->dataType ) * Element->elementsInUnit;
	}

	NewMesh->primitiveType = PRIMITIVE_TRINGLES;

	unsigned int PolygonsCount = Primitive.attribute( "count" ).as_uint();
	const char* VCountArrayContent = Primitive.child( "vcount" ).child_value();
	unsigned int PolygonsArraySize = PolygonsCount * BytesInType( DATA_TYPE_INT );
	unsigned char* Polygons = new unsigned char[PolygonsArraySize];

	ReadNumbers( Polygons,
			     VCountArrayContent,
			     DATA_TYPE_INT,
			     PolygonsCount,
			     0xF,
			     1,
			     0 );

	unsigned int *PolygonsVertexCountArray = (unsigned int*)Polygons;
	//calculate triangle and vertex count
	int TotalVertexCount = 0;
	int TriangleCount = 0;

	for ( unsigned int i = 0; i < PolygonsCount; i++ )
	{
		TotalVertexCount += PolygonsVertexCountArray[i];
		TriangleCount += PolygonsVertexCountArray[i] - TRIANGLE_VERTEX_COUNT + 1;
	}

	const char* PArrayContent = Primitive.child( "p" ).child_value();
	unsigned int VertexConstructArraySize = TotalVertexCount *
			                       	   	    DataInputs.size() *
			                       	   	    BytesInType( DATA_TYPE_INT );
	unsigned char* VertexCostructArray = new unsigned char[VertexConstructArraySize];

	ReadNumbers( VertexCostructArray,
			     PArrayContent,
			     DATA_TYPE_INT,
			     TotalVertexCount * DataInputs.size(),
			     0xF,
			     1,
			     0 );

	int IndexCount = TriangleCount * TRIANGLE_VERTEX_COUNT;
	NewMesh->triangleArray.reserve( IndexCount );

	NewMesh->vertexArray = new unsigned char[VertexSizeInBytes * TotalVertexCount];
	NewMesh->vertexCount = TotalVertexCount;
	NewMesh->vertexSizeBytes = VertexSizeInBytes;

	unsigned int *VertexCostructIndexesArray = (unsigned int*)VertexCostructArray;
	unsigned int ProcessedVertexCount = 0;

	for ( unsigned int i = 0; i < PolygonsCount; i++ )
	{
		cout << i << " Vertex: " << PolygonsVertexCountArray[i] << " ProcessedVertexCount: " << ProcessedVertexCount << endl;
		for ( unsigned int j = 0; j < PolygonsVertexCountArray[i]; j++ )
		{
			int OffsetFromVertexBegin = 0;
			for ( unsigned int k = 0; k < DataInputs.size(); k++ )
			{
				int IndexInInput = VertexCostructIndexesArray[ ( ( j + ProcessedVertexCount) *
				                                                 DataInputs.size() ) + k ];
				const MODEL_DATA_INPUT *DataInput = DataInputs[k];
				const int InputSize = BytesInType( DataInput->dataType ) * DataInput->elementsInUnit;

				memcpy( (void*)(NewMesh->vertexArray +
						       ( ProcessedVertexCount * VertexSizeInBytes ) +
						       OffsetFromVertexBegin ),
						(void*)(DataInput->dataPointer + ( IndexInInput * InputSize )),
						InputSize );

				OffsetFromVertexBegin += InputSize;
				cout << " IndexInInput[" << ( ( j + ProcessedVertexCount) *
                                              DataInputs.size() + k) << "]: " << IndexInInput << " InputSize: " << InputSize;
			}
			cout << endl;
		}

		for ( unsigned int j = 0; j <= PolygonsVertexCountArray[i] - TRIANGLE_VERTEX_COUNT; j++ )
		{
			for ( unsigned int k = 0; k < TRIANGLE_VERTEX_COUNT; k++ )
			{
				NewMesh->triangleArray.push_back( ProcessedVertexCount + j + k );
			}
		}

		ProcessedVertexCount += PolygonsVertexCountArray[i];
	}
}

bool ValidateArrayType( xml_node Array, const DATA_TYPE DataType  )
{
	bool Result = false;
	const char* NodeName = Array.name();

	switch ( DataType )
	{
		break; case DATA_TYPE_FLOAT:
		       case DATA_TYPE_FLOAT4x4:
		{
			Result = ( strcmp( NodeName, "float_array" ) == 0 );
		}
		break; case DATA_TYPE_INT:
		{
			Result = ( strcmp( NodeName, "int_array" ) == 0 );
		}
		break; case DATA_TYPE_NAME:
		{
			Result = ( strcmp( NodeName, "Name_array" ) == 0 );
		}
		break; default:
		{
			cout << "Error: Incorrect array type!" << endl;
			assert( false );
		}
	}
	return Result;
};

unsigned char*
ParseSourceArray( xml_node Array,
		          const DATA_TYPE DataType,
		          const unsigned int ReadingMask,
		          const unsigned int Stride,
		          const unsigned int Offset,
		          int &ParsedNumbers )
{
	unsigned char* Result= NULL;
	assert( ValidateArrayType( Array, DataType ) );

	unsigned int ElementsCount = Array.attribute( "count" ).as_uint();

	const char* ArrayContent = Array.child_value();

	if ( DATA_TYPE_NAME == DataType )
	{
		unsigned int ResultArraySize = strlen( ArrayContent ) + 1; // +1 for '\0'
		Result = new unsigned char[ ResultArraySize ];
		memcpy( Result, ArrayContent, ResultArraySize );
	}
	else
	{
		unsigned int ResultArraySize = ( ElementsCount - Offset ) * BytesInType( DataType );
		Result = new unsigned char[ ResultArraySize ];

		ParsedNumbers = ReadNumbers( Result,
									 ArrayContent,
									 DataType,
									 ElementsCount,
									 ReadingMask,
									 Stride,
									 Offset );
	}

	return Result;
}

DATA_TYPE
ParseAccessor( xml_node Accessor,
		       unsigned int &Offset,
		       unsigned int &Count,
		       unsigned int &Stride,
		       unsigned int &ParsingMap )
{
	DATA_TYPE Result = DATA_TYPE_INCORRECT;

	assert( Accessor );
	ParsingMap = 0;
	Offset = 0;

	xml_attribute OffsetAtribute = Accessor.attribute( "offset" );
	if ( OffsetAtribute )
	{
		Offset = OffsetAtribute.as_uint();
	}

	Count =  Accessor.attribute( "count" ).as_uint();
	Stride = Accessor.attribute( "stride" ).as_uint();

	xml_object_range<xml_named_node_iterator> Params = Accessor.children( "param" );
	bool FirstParamProcess = true;
	unsigned int ValidParamCounter = 0;

	for ( xml_named_node_iterator Begin = Params.begin(), End =  Params.end();
	      Begin != End; Begin++ )
	{
		xml_node CurrentParam = *Begin;
		bool ParamTypeIsTheSame = true;

		if ( CurrentParam.attribute( "name" ).value() )
		{
			DATA_TYPE CurrentParamType = StringToDataType( CurrentParam.attribute( "type" ).value() );

			if ( FirstParamProcess )
			{
				Result = CurrentParamType;
				FirstParamProcess = false;
			}
			else
			{
				ParamTypeIsTheSame = ( Result == CurrentParamType );
			}

			ParsingMap |= 1 << ValidParamCounter;
			ValidParamCounter++;
		}
		else
		{
			ValidParamCounter++;
		}

		// Types between params don't match
		if ( false == ParamTypeIsTheSame )
		{
			Result = DATA_TYPE_INCORRECT;
			break;
		}

	}

	return Result;
}

bool
ReadInputBlock( const char* Source, xml_node MeshNode, MODEL_DATA_INPUT *DataOutput )
{
	bool Result = false;

	if ( ( ( Source != NULL ) && ( MeshNode ) && ( DataOutput != NULL ) ) )
	{
		// Looking for the source by ID
		xml_node Input = MeshNode.find_child_by_attribute( "id", Source );
		if ( Input )
		{
			xml_node NestedInput = Input.child( "input" );
			if ( NestedInput )
			{
				Result = ReadInputBlock(
						        DEREFERENCE_INPUT_NAME( NestedInput.attribute( "source" ).value() ),
								MeshNode,
								DataOutput );
			}
			else
			{
				xml_node Accessor = Input.child( "technique_common" ).child( "accessor" );

				unsigned int OffsetInDataBlock;
				unsigned int ParsingMap; // Bit mask for elements to be processed
				DataOutput->dataType = ParseAccessor( Accessor,
						                              OffsetInDataBlock,
						                              DataOutput->unitsCount,
						                              DataOutput->elementsInUnit,
						                              ParsingMap );

				assert( DataOutput->dataType != DATA_TYPE_INCORRECT );

				const char* OriginalTempName = Accessor.attribute( "source" ).value();
				const char* TmpName = DEREFERENCE_INPUT_NAME( OriginalTempName );
				find_source_predicate::SourceId = TmpName;

				xml_node DataArray = MeshNode.find_node( find_source_predicate() );


				int ParsedNumbers;
				DataOutput->dataPointer = ParseSourceArray( DataArray,
										  	  	  	  	    DataOutput->dataType,
										  	  	  	  	    ParsingMap,
										  	  	  	  	    DataOutput->elementsInUnit,
										  	  	  	  	    OffsetInDataBlock,
										  	  	  	  	    ParsedNumbers );
				Result = DataOutput->dataPointer != NULL;
			}
		}

	}

	return Result;
}




vector<MESH_GROUP*>
LoadMeshes( xml_document &ColladaDocument )
{
	vector<MESH_GROUP*> Result;

	//Select all meshes
	xpath_node_set Meshes = ColladaDocument.select_nodes(
			                   "/COLLADA/library_geometries/geometry/mesh" );
	for ( xpath_node_set::const_iterator Begin = Meshes.begin(), End =  Meshes.end();
	      Begin != End; Begin++ )
	{
	    xpath_node  MeshNode  = *Begin;
	    MESH_GROUP* MeshGroup = NULL;

	    string MeshName = string( MeshNode.node().parent().attribute( "name" ).value() );

	    //xml_node Primitives =
	    //		MeshNode.node().find_node( geometry_primitive_predicate() );

    	vector< pair<xml_node, vector<MODEL_DATA_INPUT*> > > PrimitivesNodes;
	    int IndexInNamesArray = 0;
	    bool ValidPrimitiveTypeFound = false;
	    // PRIMITIVE_POSSIBLE_NAMES is a null-terminated array
	    while ( ( PRIMITIVE_POSSIBLE_NAMES[IndexInNamesArray][0] != 0 ) &&
	    		!ValidPrimitiveTypeFound )
	    {
	    	xml_object_range<xml_named_node_iterator> Primitives =
	    			                   MeshNode.node().children( PRIMITIVE_POSSIBLE_NAMES[IndexInNamesArray] );
	    	if ( Primitives.begin() != Primitives.end() )
	    	{
	    		ValidPrimitiveTypeFound = true;

	    		for ( xml_named_node_iterator Begin = Primitives.begin(), End = Primitives.end();
	    			  Begin != End; Begin++ )
	    		{
	    			xml_node CurrentPrimitive = *Begin;

	    		    vector<MODEL_DATA_INPUT*> DataInputs;
	    		    DataInputs.reserve( ESTIMATED_INPUTS_COUNT );

	    		    for ( xml_node Input = CurrentPrimitive.child( "input" );
	    		          Input;
	    		          Input = Input.next_sibling( "input" ) )
	    		    {
	    		    	// Skip an empty attribute
	    		    	if ( Input.attribute( "semantic" ).value() )
	    		    	{
	    		    		MODEL_DATA_INPUT* NewDataInput = new MODEL_DATA_INPUT();
	    		    		NewDataInput->dataSemantic = StringToDataSemantic( Input.attribute( "semantic" ).value() );
	    		    		assert( ReadInputBlock(
	    							        DEREFERENCE_INPUT_NAME( Input.attribute( "source" ).value() ),
	    							        MeshNode.node(),
	    							        NewDataInput ) );

	    		    		NewDataInput->offset = Input.attribute( "offset" ).as_int();
	    		    		DataInputs.push_back( NewDataInput );
	    		    	}
	    		    }

	    		    sort( DataInputs.begin(), DataInputs.end(), sort_inputs_by_offset() );
	    			PrimitivesNodes.push_back(
	    					        pair<xml_node, vector<MODEL_DATA_INPUT*> >( CurrentPrimitive, DataInputs ) );
	    		}
	    	}
	    	else
	    	{
	    		IndexInNamesArray++;
	    	}
	    }

    	if ( PrimitivesNodes.size() > 0 )
    	{
    		MeshGroup = new MESH_GROUP();
    	}

	    if ( strcmp( PRIMITIVE_POSSIBLE_NAMES[IndexInNamesArray], "triangles" ) == 0 )
	    {
    		for ( vector< pair<xml_node, vector<MODEL_DATA_INPUT*> > >::iterator Begin = PrimitivesNodes.begin(),
    				                                                             End = PrimitivesNodes.end();
    			  Begin != End;
    			  Begin++ )
    		{
    			pair<xml_node, vector<MODEL_DATA_INPUT*> > CurrentObject = *Begin;

    		    MESH* NewMesh = new MESH();
	    		NewMesh->name = MeshName;

	    		ParsePrimitiveTriangles( CurrentObject.first, CurrentObject.second, NewMesh );

	    		MeshGroup->meshes.push_back( NewMesh );
	    	}
	    }
	    else if ( strcmp( PRIMITIVE_POSSIBLE_NAMES[IndexInNamesArray], "polygons" ) == 0 )
	    {
	    	cout << "Error: Polygons are not supported yet" << endl;
	    	assert( false );
	    }
	    else if ( strcmp( PRIMITIVE_POSSIBLE_NAMES[IndexInNamesArray], "polylist" ) == 0 )
	    {
    		for ( vector< pair<xml_node, vector<MODEL_DATA_INPUT*> > >::iterator Begin = PrimitivesNodes.begin(),
    				                                                             End = PrimitivesNodes.end();
    			  Begin != End;
    			  Begin++ )
    		{
    			pair<xml_node, vector<MODEL_DATA_INPUT*> > CurrentObject = *Begin;

    		    MESH* NewMesh = new MESH();
	    		NewMesh->name = MeshName;

	    		ParsePrimitivePolylist( CurrentObject.first, CurrentObject.second, NewMesh );

	    		MeshGroup->meshes.push_back( NewMesh );
	    	}
	    }
	    else if ( strcmp( PRIMITIVE_POSSIBLE_NAMES[IndexInNamesArray], "tristrips" ) == 0 )
	    {
	    	cout << "Error: Tristrips are not supported yet" << endl;
	    	assert( false );
	    }

		for ( vector< pair<xml_node, vector<MODEL_DATA_INPUT*> > >::iterator Begin = PrimitivesNodes.begin(),
				                                                             End = PrimitivesNodes.end();
			  Begin != End;
			  Begin++ )
		{
			pair<xml_node, vector<MODEL_DATA_INPUT*> > CurrentObject = *Begin;

			for ( vector<MODEL_DATA_INPUT*>::iterator Begin = CurrentObject.second.begin(),
					                                  End = CurrentObject.second.end();
				  Begin != End;
				  Begin++ )
			{
				MODEL_DATA_INPUT* Element = *Begin;
				delete Element;
			}
		}

	    if ( MeshGroup != NULL )
	    {
	    	Result.push_back( MeshGroup );
	    }
	}

	return Result;
}

bool
LoadJoints( xml_node JointsNode, SKINNING_CONTROLLER *Controller )
{
	assert( JointsNode );

	xml_node JointInput = JointsNode.find_child_by_attribute( "input", "semantic", "JOINT" );
	assert( JointInput );

	MODEL_DATA_INPUT JointsDataInput;
	bool Result = ReadInputBlock( DEREFERENCE_INPUT_NAME( JointInput.attribute( "source" ).value() ),
			                      JointsNode.parent(),
			                      &JointsDataInput );

	xml_node InverseBindMatrixInput = JointsNode.find_child_by_attribute( "input",
			                                                              "semantic",
			                                                              "INV_BIND_MATRIX" );
	if ( Result )
	{
		MODEL_DATA_INPUT BindMatrixDataInput;

		if ( InverseBindMatrixInput )
		{
			Result = ReadInputBlock( DEREFERENCE_INPUT_NAME(
					                      InverseBindMatrixInput.attribute( "source" ).value() ),
					                 JointsNode.parent(),
								     &BindMatrixDataInput );
		}
		else
		{
			assert( false );
			// TODO Fill this case with identity bind matrix
		}

		if ( Result )
		{
			Controller->jointsCount = JointsDataInput.unitsCount;
			Controller->jointNames.reserve( Controller->jointsCount );

			for ( int i = 0; i < Controller->jointsCount; i++ )
			{
				char* JointsNamesString = (char*)JointsDataInput.dataPointer;
				string TempJointName( strtok( (( 0 == i ) ? JointsNamesString : NULL), " \n" ) );
				Controller->jointNames.push_back( TempJointName );
			}

			Controller->jointsArray = (float*)BindMatrixDataInput.dataPointer;
			BindMatrixDataInput.dataPointer = NULL;
		}
	}

	return Result;
}

bool
LoadVertexWeights( xml_node WeightNode, SKINNING_CONTROLLER *Controller )
{
	bool Result = false;

	int VertexWithWeightCount = WeightNode.attribute( "count" ).as_int();

	//Assumption that only one joint array in a skin
	//xml_node JointInputNode = WeightNode.find_child_by_attribute( "input", "semantic", "JOINT" );

	xml_node WeightInputNode = WeightNode.find_child_by_attribute( "input", "semantic", "WEIGHT" );
	MODEL_DATA_INPUT WeightInputs;
	Result = ReadInputBlock( DEREFERENCE_INPUT_NAME( WeightInputNode.attribute( "source" ).value() ),
			                 WeightNode.parent(),
			                 &WeightInputs );

	xml_node ReadNode = WeightNode.child( "vcount" );
	int* AttributesPerVertexArray = new int[VertexWithWeightCount];
	Controller->weightPerVertexArray = AttributesPerVertexArray;

	ReadNumbers( (unsigned char*)AttributesPerVertexArray,
			     ReadNode.child_value(),
			     DATA_TYPE_INT,
			     VertexWithWeightCount );

	int TotalAttributePairsCount = 0;
	for ( int i = 0; i < VertexWithWeightCount; i++ )
	{
		TotalAttributePairsCount += AttributesPerVertexArray[i];
	}

	ReadNode = WeightNode.child( "v" );
	float* WeightBonesPerVertexArray = new float[TotalAttributePairsCount];
	Controller->vertexWeightArray = WeightBonesPerVertexArray;

	ReadNumbers( (unsigned char*)WeightBonesPerVertexArray,
			     ReadNode.child_value(),
			     DATA_TYPE_FLOAT,
			     TotalAttributePairsCount );

	return Result;
}

vector<SKINNING_CONTROLLER*>
LoadSkinningController( xml_document &ColladaDocument )
{
	vector<SKINNING_CONTROLLER*> Result;

	//Select all skinning
	xpath_node_set Skinning = ColladaDocument.select_nodes(
			                   "/COLLADA/library_controllers/controller/skin" );
	for ( xpath_node_set::const_iterator Begin = Skinning.begin(), End =  Skinning.end();
	      Begin != End; Begin++ )
	{
		xml_node Skin = Begin->node();
	    SKINNING_CONTROLLER* NewController = new SKINNING_CONTROLLER();

	    NewController->name = string( Skin.parent().attribute( "id" ).value() );
	    NewController->meshName = string( DEREFERENCE_INPUT_NAME( Skin.attribute( "source" ).value() ) );

	    //Get bind shape matrix
	    xml_node BindShapeMatrix = Skin.child( "bind_shape_matrix" );
		ReadNumbers( (unsigned char*)NewController->bingShapeMatrix,
				     BindShapeMatrix.child_value(),
				     DATA_TYPE_FLOAT,
				     MATRIX4x4_ELEMENTS_COUNT );

		LoadJoints( Skin.child( "joints" ), NewController );
		LoadVertexWeights( Skin.child( "vertex_weights" ), NewController );
		Result.push_back( NewController );

	}

	return Result;
}

glm::mat4
ParseBodeMatrix( xml_node AnimationNode )
{
	glm::mat4 Result( 1.0 );

	//Always process scaling first
	xml_node ScaleParam = AnimationNode.child( "scale" );
	if ( ScaleParam )
	{
		float Scalers[3];
		ReadNumbers( (unsigned char*)Scalers,
				     ScaleParam.child_value(),
				     DATA_TYPE_FLOAT,
				     3 );
		Result = glm::scale( Result, glm::vec3( Scalers[0], Scalers[1], Scalers[2] ) );
	}

	xml_object_range<xml_named_node_iterator> Rotates = AnimationNode.children( "rotate" );
	for ( xml_named_node_iterator Begin = Rotates.begin(), End = Rotates.end();
		  Begin != End; Begin++ )
	{
		float RotationConfig[4];
		ReadNumbers( (unsigned char*)RotationConfig,
				     Begin->child_value(),
				     DATA_TYPE_FLOAT,
				     4 );
		Result = glm::rotate( Result,
				              RotationConfig[3],
				              glm::vec3( RotationConfig[0], RotationConfig[1], RotationConfig[2] ) );
	}

	//Translation is the last stage
	xml_node TranslateParam = AnimationNode.child( "translate" );
	if ( TranslateParam )
	{
		float Movement[3];
		ReadNumbers( (unsigned char*)Movement,
				     TranslateParam.child_value(),
				     DATA_TYPE_FLOAT,
				     3 );
		Result = glm::translate( Result, glm::vec3( Movement[0], Movement[1], Movement[2] ) );
	}

	return Result;
}

BONE*
ParseAnimationJoints( xml_node AnimationNode )
{
	BONE* Result = NULL;

	if ( AnimationNode )
	{
		Result = new BONE();

		Result->name = string( AnimationNode.attribute( "name" ).value() );

		xml_attribute id = AnimationNode.attribute( "id" );
		if ( !id )
		{
			id = AnimationNode.attribute( "sid" );
		}
		Result->id = string( id.value() );

		Result->matrix = ParseBodeMatrix( AnimationNode );

		xml_object_range<xml_named_node_iterator> Nodes = AnimationNode.children( "node" );
		for ( xml_named_node_iterator Begin = Nodes.begin(), End = Nodes.end();
			  Begin != End; Begin++ )
		{
			BONE* SubNode = ParseAnimationJoints( *Begin );
			if ( SubNode != NULL )
			{
				Result->childBones.push_back( SubNode );
			}
		}
	}

	return Result;
}

vector<SKINNING_ANIMATION*>
LoadSkinningAnimation( xml_document &ColladaDocument )
{
	vector<SKINNING_ANIMATION*> Result;

	xpath_node_set Skinning = ColladaDocument.select_nodes(
			                   "/COLLADA/library_visual_scenes/visual_scene" );

	for ( xpath_node_set::const_iterator _Begin = Skinning.begin(), _End =  Skinning.end();
	      _Begin != _End; _Begin++ )
	{
		xml_node SceneNode = _Begin->node();
		xml_object_range<xml_named_node_iterator> Nodes = SceneNode.children( "node" );
		vector< pair<string, string> > ControllerInstancesArray;
		boost::unordered_map< string, BONE* > SkeletonMap;

		for ( xml_named_node_iterator __Begin = Nodes.begin(), __End = Nodes.end();
			  __Begin != __End; __Begin++ )
		{
			xml_node Node = *__Begin;
			const char* NodeType = Node.attribute( "type" ).value();

			if ( strcmp( NodeType, "JOINT" ) == 0 )
			{
				BONE* Bones = ParseAnimationJoints( Node );
				string BonesName = string( Node.attribute( "id" ).value() );
				SkeletonMap.emplace( BonesName, Bones );
			}
			else
			{
				xml_node TmpNode = Node.child( "instance_controller" );
				string SkinToAnimate = DEREFERENCE_INPUT_NAME( TmpNode.attribute( "url" ).value() );

				TmpNode = TmpNode.child( "skeleton" );
				string SkeletonRoot = DEREFERENCE_INPUT_NAME( TmpNode.child_value() );

				ControllerInstancesArray.push_back( pair<string, string>( SkinToAnimate, SkeletonRoot ) );
			}
		}

		xml_node TechniqueNode = SceneNode.child( "extra" ).child( "technique" );

		for ( vector< pair<string, string> >::const_iterator Begin = ControllerInstancesArray.begin(),
			                                                 End = ControllerInstancesArray.end();
			  Begin != End;
			  Begin++ )
		{
			SKINNING_ANIMATION* NewSkinningAnimation = new SKINNING_ANIMATION();

			pair<string, string> CurrentElement = *Begin;
			NewSkinningAnimation->controllerName = CurrentElement.first;
			NewSkinningAnimation->animationName = SceneNode.attribute( "name" ).value();
			NewSkinningAnimation->startTime = strtof( TechniqueNode.child( "start_time" ).child_value(),
					                                  NULL );
			NewSkinningAnimation->endTime = strtof( TechniqueNode.child( "end_time" ).child_value(),
					                                  NULL );

			string FirstName = CurrentElement.first;
			string SecondName = CurrentElement.second;
			BONE* ActiveBone = SkeletonMap[ CurrentElement.second ];
			assert( ActiveBone );
			NewSkinningAnimation->bones = ActiveBone;

			Result.push_back( NewSkinningAnimation );
		}
	}

	return Result;
}

LOADED_3D_INFO*
ParseColladaFile( const string InputFileName, const unsigned int LoadOption )
{
	LOADED_3D_INFO  *Result = NULL;
	xml_document     ColladaDocument;
	xml_parse_result ParsingResult = ColladaDocument.load_file( InputFileName.c_str() );

	if ( ParsingResult.status == status_ok )
	{
		Result = new LOADED_3D_INFO();

		if ( ( LoadOption & SAVE_TO_BINARY_MESH ) == SAVE_TO_BINARY_MESH )
		{
			Result->models = LoadMeshes( ColladaDocument );
		}

		if ( ( LoadOption & SAVE_TO_BINARY_CONTROLLER ) == SAVE_TO_BINARY_CONTROLLER )
		{
			Result->controllers = LoadSkinningController( ColladaDocument );
		}

		if ( ( LoadOption & SAVE_TO_BINARY_ANIMATION ) == SAVE_TO_BINARY_ANIMATION )
		{
			Result->animations = LoadSkinningAnimation( ColladaDocument );
		}
	}
	else
	{
	    cout << "Error: " << ParsingResult.description() << endl;
	}

	return Result;
}

bool
Save3DInfo( LOADED_3D_INFO* Loaded3DInfo,
		    unsigned int SaveToBinaryOption,
		    string OutputFileName )
{
	ofstream OutputFile( OutputFileName.c_str(), ios::ate | ios::binary );

	bool Result = false;

	if ( OutputFile.is_open() )
	{
		boost::archive::binary_oarchive OutputArchive( OutputFile );

		OutputArchive << SaveToBinaryOption;

		if ( ( SaveToBinaryOption & SAVE_TO_BINARY_MESH ) == SAVE_TO_BINARY_MESH )
		{
			unsigned long SizeVariable = 0;
			SizeVariable = Loaded3DInfo->models.size();
			OutputArchive << SizeVariable;
			for ( vector<MESH_GROUP*>::const_iterator Begin = Loaded3DInfo->models.begin(),
					                            End = Loaded3DInfo->models.end();
				  Begin != End;
				  Begin++ )
			{
				OutputArchive << *Begin;
			}
		}

		if ( ( SaveToBinaryOption & SAVE_TO_BINARY_CONTROLLER ) == SAVE_TO_BINARY_CONTROLLER )
		{
			unsigned long SizeVariable = 0;
			SizeVariable = Loaded3DInfo->models.size();
			OutputArchive << SizeVariable;
			for ( vector<SKINNING_CONTROLLER*>::const_iterator Begin = Loaded3DInfo->controllers.begin(),
					                                           End = Loaded3DInfo->controllers.end();
				  Begin != End;
				  Begin++ )
			{
				OutputArchive << *Begin;
			}
		}

		if ( ( SaveToBinaryOption & SAVE_TO_BINARY_ANIMATION ) == SAVE_TO_BINARY_ANIMATION )
		{
			unsigned long SizeVariable = 0;
			SizeVariable = Loaded3DInfo->models.size();
			OutputArchive << SizeVariable;
			for ( vector<SKINNING_ANIMATION*>::const_iterator Begin = Loaded3DInfo->animations.begin(),
					                                          End = Loaded3DInfo->animations.end();
				  Begin != End;
				  Begin++ )
			{
				OutputArchive << *Begin;
			}
		}

		Result = true;
	}

	return Result;
}

void
ListPossibleOptions()
{

}

int
main( int argc, char *argv[] )
{

#ifdef DEBUG_INPUT
	cout << sizeof(argc) << endl;
	for ( int i = 0; i < argc; i++ )
	{
		cout << i << ": " << argv[i] << endl;
	}
#endif

	string OutputFileName = DEFAULT_OUTPUT_FILE;
	string FinalMessage = DEFAULT_FINAL_MESSAGE;
	unsigned int SaveToBinaryOption = SAVE_TO_BINARY_MESH |
			                          SAVE_TO_BINARY_CONTROLLER |
			                          SAVE_TO_BINARY_ANIMATION;

	for ( int i = 1; i < argc; i++ )
	{
		// Is this argument an option
		if ( INPUT_OPTION_FIRST_SYMBOL == argv[i][0] )
		{
			if ( strcmp( argv[i], "-o" ) == 0 )
			{
				i++;
				OutputFileName = string( argv[i] );
			}
			else if ( strcmp( argv[i], "-no_mesh" ) == 0 )
			{
				SaveToBinaryOption &= ~SAVE_TO_BINARY_MESH;
			}
			else if ( strcmp( argv[i], "-no_cntr" ) == 0 )
			{
				SaveToBinaryOption &= ~SAVE_TO_BINARY_CONTROLLER;
			}
			else if ( strcmp( argv[i], "-no_anim" ) == 0 )
			{
				SaveToBinaryOption &= ~SAVE_TO_BINARY_ANIMATION;
			}
			else if ( strcmp( argv[i], "-help" ) == 0 )
			{
				ListPossibleOptions();
				break;
			}
			else
			{
				FinalMessage = INVALID_ARGUMENT_MESSAGE + string( argv[i] );
				break;
			}
		}
		else
		{
			string InputFileName( argv[i] );
			i++;

			if ( i < argc )
			{
				OutputFileName = argv[i];
			}

			LOADED_3D_INFO* LoadedContent = ParseColladaFile( InputFileName, SaveToBinaryOption );

			if ( LoadedContent != NULL )
			{
				Save3DInfo( LoadedContent, SaveToBinaryOption, OutputFileName );
				FinalMessage = ALL_FINISHED_SUCCESSFULLY_MESAGE;
			}
			else
			{
				FinalMessage = COULD_NOT_LOAD_INPUT_MESSAGE;
			}

			delete LoadedContent;

			break;
		}
	}

	cout << FinalMessage.c_str() << endl;
	return 0;
}

const char* geometry_primitive_predicate::primitive_possible_names[] = { "triangles", "polygons", "polylist", "tristrips" };
const char* find_source_predicate::SourceId = NULL;
